import pygame
import os
import sys
import random


W = 600
H = 600
COL = 40
ROW = 40
size = (W, H)
color_snake = (252, 26, 252)
color_food = (255, 150, 40)
color_grid = (255, 0, 0)
color_star = [0, 0, 0]
color_bg = (0, 0, 0)


class Game(object):
    def __init__(self):
        self.score = 0
        self.gametime = 0
        self.musictime = 0
        self.fps = 15

    def restart(self):
        self.score = 0
        self.gametime = 0
        self.fps = 15

    def speed_score(self):
        if self.gametime % 40 == 1:
            self.score += 1
        if self.fps < 18 and self.gametime % 125 == 124:
            self.fps += 1
        elif 18 <= self.fps < 27 and self.gametime % 300 == 299:
            self.fps += 1
        elif self.fps >= 27 and self.gametime % 450 == 449:
            self.fps += 1
        

class Node:
    def __init__(self, row, col, color):
        self.row = row
        self.col = col
        self.color = color

    def copy(self):
        return Node(row=self.row, col=self.col, color=self.color)


class Snake(object):
    def __init__(self):
        self.head = Node(
            row=random.randint(
                15, 30), col=random.randint(
                15, 30), color=color_snake)
        self.body = [
            Node(row=self.head.row, col=self.head.col + 1, color=color_snake),
            Node(row=self.head.row, col=self.head.col + 2, color=color_snake),
            Node(row=self.head.row, col=self.head.col + 3, color=color_snake),
            Node(row=self.head.row, col=self.head.col + 4, color=color_snake),
            Node(row=self.head.row, col=self.head.col + 5, color=color_snake),
            Node(row=self.head.row, col=self.head.col + 6, color=color_snake)]
        self.deadbody = []
        self.direct = 'left'
        self.fade_flag = False
        self.fadetime = 0

    def move(self, meteor):
        global food1
        eat_flag = (self.head.row == food1.row and self.head.col == food1.col)
        if eat_flag:
            game.score += 5
            food1 = gen_food(self, meteor)
        # 蛇身变化
        self.body.insert(0, self.head.copy())
        if not eat_flag:
            self.body.pop()
        # 蛇头移动
        if self.direct == 'left':
            self.head.col -= 1
        elif self.direct == 'right':
            self.head.col += 1
        elif self.direct == 'up':
            self.head.row -= 1
        elif self.direct == 'down':
            self.head.row += 1
        i = 0
        for s in self.body:
            i += 1
            temp = (
                (color_snake[0] - 7 * i) if color_snake[0] - 7 * i > 10 else 0,
                color_snake[1],
                (color_snake[2] - 7 * i) if color_snake[2] - 7 * i > 10 else 0)
            s.color = temp

    def isdead(self, meteor):
        dead_flag = False
        if self.head.col < 0 or self.head.row < 0 or self.head.col >= COL or self.head.row >= ROW:
            dead_flag = True
        if meteor.posx - 15 < self.head.col * 15 + 7 < meteor.posx + \
                15 and meteor.posy - 15 < self.head.row * 15 + 7 < meteor.posy + 15:
            dead_flag = True
        for s in self.body:
            if self.head.col == s.col and self.head.row == s.row:
                dead_flag = True
                break
        return dead_flag

    def iscut(self, meteor):
        global game
        cut_flag = False
        num = 0
        for s in self.body:
            num += 1
            if meteor.posx - 15 < s.col * 15 + 7 < meteor.posx + 15\
                    and meteor.posy - 15 < s.row * 15 + 7 < meteor.posy + 15:
                self.fadetime = game.gametime
                cut_flag = True
                self.fade_flag = True
                game.score -= ((len(self.body) - num) * 3)
                break
        if cut_flag:
            for i in range(len(self.body) - num + 1):
                self.deadbody.insert(0, self.body[-1])
                self.body.pop()
        if self.fade_flag:
            if self.deadbody[-1].color[0] != 0 and self.deadbody[-1].color[1] != 0:
                for s in self.deadbody:
                    i = game.gametime - self.fadetime
                    temp = (
                        (s.color[0] - i) if s.color[0] - i > 2 else 0,
                        s.color[1],
                        (s.color[2] - i) if s.color[2] - i > 2 else 0)
                    s.color = temp
            else:
                self.deadbody.pop()
            if len(self.deadbody) == 0:
                self.fadetime = 0
                self.fade_flag = False


def gen_food(snake, meteor):
    while True:
        pos = Node(
            row=random.randint(2,ROW - 2),
            col=random.randint(2,COL - 2),
            color=color_food)
        is_coll = False
        if snake.head.row == pos.row and snake.head.col == pos.col:
            is_coll = True
        if 1 < pos.row < 6 and 13 < pos.col < ROW - 13:
            is_coll = True
        if meteor.posx - 15 < pos.col * 15 + 7 < meteor.posx + 15\
                or meteor.posy - 15 < pos.row * 15 + 7 < meteor.posy + 15:
            is_coll = True
        for s in snake.body:
            if s.row == pos.row and s.col == pos.col:
                is_coll = True
                break
        if not is_coll:
            return pos


def blur_surface(surface, level):
    if level < 1.0:
        raise ValueError("'level'值必须大于 1.0, 传入值为 %s" % level)
    scale = 1.0 / float(level)
    surf_size = surface.get_size()
    scale_size = (int(surf_size[0] * scale), int(surf_size[1] * scale))
    surf = pygame.transform.smoothscale(surface, scale_size)
    surf = pygame.transform.smoothscale(surf, surf_size)
    return surf


def rect(surf, node):
    cell_width = W / COL
    cell_height = H / ROW
    left = node.col * cell_width
    top = node.row * cell_height
    pygame.draw.rect(surf, node.color, (left, top, cell_width, cell_height))


def create_text(message, mysize, mycolor, pos, mytype):
    myfont = pygame.font.Font(r"fonts\{}.ttf".format(mytype), mysize)
    mytext = myfont.render(message, True, mycolor)
    window.blit(mytext, (pos[0] - mytext.get_width() / 2, pos[1]))


def start_menu():
    pygame.mixer.music.load(r"music\menu.mp3")
    pygame.mixer.music.play(-1)
    pygame.mixer.music.set_volume(1.0)
    layers[0] = pygame.image.load(r"pics\0.jpg")
    window.blit(layers[0], (0, 0))
    pygame.display.update()
    pygame.time.wait(2000)

    for i in range(180, 0, -1):
        cover.fill((i, i, i))
        window.blit(cover, (0, 0))
        pygame.display.update()

    temp = 0
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    for i in range(180, 0, -1):
                        cover.fill((i, i, i))
                        window.blit(cover, (0, 0))
                        pygame.display.update()
                        pygame.mixer.music.set_volume(i / 180 * 1.0)
                    return

        cover.fill((0, 0, 0))
        layers[0] = pygame.image.load(
            r"pics\pic ({}).png".format(temp % 22 + 1))
        window.blit(layers[0], (0, 0))
        if temp < 10:
            create_text("Retrowave ", 45 + temp // 2,
                        (254, 254, 254), (W / 2, H / 2 - 120), 'gamer2')
            create_text("Snake ", 45 + temp // 2, (254, 254, 254),
                        (W / 2, H / 2 - 60), 'gamer2')
        else:
            create_text("Retrowave ", 50 + (temp % 10) // 5,
                        (254, 254, 254), (W / 2, H / 2 - 120), 'gamer2')
            create_text("Snake ", 50 + (temp % 10) // 5,
                        (254, 254, 254), (W / 2, H / 2 - 60), 'gamer2')
        if (temp % 22) < 11:
            create_text("Press SPACE to start", 18,
                        (254, 254, 254), (W / 2, 530), 'cyber')
        pygame.display.update()
        temp += 1
        clock.tick(30)


star_list = []


def draw_star(surface, offset, dir1, nums):
    global color_star
    if dir1 == 0:
        offsetx = offset
        offsety = offset
    elif dir1 == 1:
        offsetx = offset
        offsety = -offset
    elif dir1 == 2:
        offsetx = -offset
        offsety = -offset
    else:
        offsetx = -offset
        offsety = offset
    if offset % 100 == 0:
        star_list.clear()
        for i in range(nums):
            mydict = {}
            color_star = [0, 0, 0]
            mydict['pos'] = (random.randint(0, W), random.randint(0, H))
            mydict['radius'] = random.randint(0, 5)
            star_list.append(mydict)
            pygame.draw.circle(
                surface, (color_star[0],color_star[1],color_star[2]),
                star_list[i]['pos'], star_list[i]['radius'], 0)
    elif offset % 100 <= 50:
        for i in range(3):
            if color_star[i] < 250:
                color_star[i] += 4
        for i in range(nums):
            pygame.draw.circle(
                surface, (color_star[0],color_star[1],color_star[2]),
                ((star_list[i]['pos'][0] + offsetx %100),star_list[i]['pos'][1] + offsety % 100),
                star_list[i]['radius'], 0)
    else:
        for i in range(3):
            if color_star[i] > 5:
                color_star[i] -= 4
        for i in range(nums):
            pygame.draw.circle(
                surface, (color_star[0], color_star[1], color_star[2]),
                ((star_list[i]['pos'][0] + offsetx % 100), star_list[i]['pos'][1] + offsety % 100),
                star_list[i]['radius'], 0)


def draw_grid(surface, color, offset, dir1):
    if dir1 == 0:
        offsetx = offset
        offsety = offset
    elif dir1 == 1:
        offsetx = offset
        offsety = -offset
    elif dir1 == 2:
        offsetx = -offset
        offsety = -offset
    else:
        offsetx = -offset
        offsety = offset
    for y in range(0, H, H // ROW * 5):
        for x in range(0, W, W // COL * 5):
            pygame.draw.line(surface, color, ((x + offsetx) % W, 0), ((x + offsetx) % W, H), 7)
    for x in range(0, W, W // COL * 5):
        for y in range(0, H, H // ROW * 5):
            pygame.draw.line(surface, color, (0, (y + offsety) % H), (W, (y + offsety) % H), 7)


class Meteor(object):
    def __init__(self, mydir, myspeed, food):
        self.dir = mydir
        if self.dir == 'up' or self.dir == 'down':
            self.posx = random.randint(5, 35) * 15 + 7
            while self.posx - 15 < food[0] * 15 + 7 < self.posx + 15:
                self.posx = random.randint(5, 35) * 15 + 7
            if self.dir == 'up':
                t = 1
                self.posy = 650
                self.speed = -myspeed
            else:
                t = -1
                self.posy = -50
                self.speed = myspeed
            self.line = (([self.posx - 6 * t, self.posy + 10 * t], [self.posx - 6 * t, self.posy + 26 * t]),
                         ([self.posx, self.posy + 10 * t], [self.posx, self.posy + 30 * t]),
                         ([self.posx + 6 * t, self.posy + 10 * t], [self.posx + 6 * t, self.posy + 23 * t]))
        else:
            self.posy = random.randint(5, 35) * 15 + 7
            while self.posy - 15 < food[1] * 15 + 7 < self.posy + 15:
                self.posy = random.randint(5, 35) * 15 + 7
            if self.dir == 'right':
                t = 1
                self.posx = -50
                self.speed = myspeed
            else:
                t = -1
                self.posx = 650
                self.speed = -myspeed
            self.line = (([self.posx - 10 * t, self.posy + 6 * t], [self.posx - 26 * t, self.posy + 6 * t]),
                         ([self.posx - 10 * t, self.posy], [self.posx - 30 * t, self.posy]),
                         ([self.posx - 10 * t, self.posy - 6 * t], [self.posx - 23 * t, self.posy - 6 * t]))

    def move(self):
        if self.dir == 'up' or self.dir == 'down':
            temp = 1
            self.posy += self.speed
        else:
            temp = 0
            self.posx += self.speed

        for i in range(3):
            self.line[i][0][temp] += self.speed
            self.line[i][1][temp] += self.speed

    def draw(self):
        pygame.draw.circle(foreground, (252, 110, 80), (self.posx, self.posy), 8)
        pygame.draw.circle(foreground, (246, 188, 64), (self.posx, self.posy), 5)
        pygame.draw.line(foreground, (252, 110, 80), self.line[0][0], self.line[0][1], 2)
        pygame.draw.line(foreground, (252, 110, 80), self.line[1][0], self.line[1][1], 2)
        pygame.draw.line(foreground, (252, 110, 80), self.line[2][0], self.line[2][1], 2)


def game_loop():
    global food1, game
    snake1 = Snake()
    game.restart()
    meteor = Meteor(random.choice(('up', 'down', 'left', 'right')), random.randint(1, 4), (20, 20))
    food1 = gen_food(snake1, meteor)
    dir_grid = random.randint(0, 3)
    dir_star = random.randint(0, 3)

    pygame.mixer.music.load(r"music\game1.mp3")
    pygame.mixer.music.set_volume(1.0)
    pygame.mixer.music.play(-1, game.musictime)

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                if event.key == 273 or event.key == 119:
                    if snake1.direct != 'down':
                        snake1.direct = 'up'
                elif event.key == 274 or event.key == 115:
                    if snake1.direct != 'up':
                        snake1.direct = 'down'
                elif event.key == 276 or event.key == 97:
                    if snake1.direct != 'right':
                        snake1.direct = 'left'
                elif event.key == 275 or event.key == 100:
                    if snake1.direct != 'left':
                        snake1.direct = 'right'

        # 行为判断
        snake1.move(meteor)
        snake1.iscut(meteor)
        if snake1.isdead(meteor):
            game.musictime = pygame.mixer.music.get_pos() / 1000 + game.musictime
            game.musictime = game.musictime % 608
            return
        meteor.move()

        # 画图
        window.fill(color_bg)
        for layer in layers:
            layer.fill((0, 0, 0))
        draw_star(foreground, game.gametime, dir_star, 18)
        draw_grid(foreground, color_grid, game.gametime, dir_grid)
        for s in snake1.deadbody:
            rect(foreground, s)
        if meteor.posx > 650 or meteor.posy > 650 or meteor.posx < -50 or meteor.posy < -50:
            meteor = Meteor(
                random.choice(('up', 'down', 'left', 'right')), random.randint(1, 4), (food1.col, food1.row))
        meteor.draw()
        for s in snake1.body:
            rect(foreground, s)
        rect(foreground, snake1.head)
        rect(foreground, food1)
        layers[0] = blur_surface(foreground, 15)
        for layer in layers:
            window.blit(layer, (0, 0))
        create_text("Score: {}".format(game.score), 20,
                    (254, 254, 254), (W / 2, 50), 'gamer1')
        create_text("Speed: {}".format(game.fps), 20,
                    (254, 254, 254), (W / 2, 22), 'gamer1')
        pygame.display.update()

        # 计算游戏分数、速度
        game.speed_score()
        game.gametime += 1
        clock.tick(game.fps)


def draw_sun(pos, radius, offset):
    window.fill(color_bg)
    foreground.fill((0, 0, 0))
    pygame.draw.circle(foreground, (210, 5, 118), (pos[0] + 3, pos[1] + 2), radius, 0)  # 红
    pygame.draw.circle(foreground, (80, 80, 140), (pos[0] - 4, pos[0] + 3), radius, 0)  # 蓝
    pygame.draw.circle(foreground, (245, 245, 245), (pos[0], pos[0]), radius, 0)  # 白
    for i in range(7):
        y = pos[1] + radius - (15 * i + offset) % (radius - 15)
        mywidth = 7 - ((15 * i + offset) % (radius - 15)) // 15
        pygame.draw.line(foreground, (0, 0, 0), (pos[0] - radius, y), (pos[0] + radius, y), mywidth)
    layers[0] = blur_surface(foreground, 70)
    window.blit(background, (0, 0))
    window.blit(foreground, (0, 0))
    create_text("2020 Meteora", 20, (165, 0, 0), (W / 2, 20), 'Neon-Glow')
    for i in range(1, 3):
        create_text("2020 Meteora", 20, (165, 0, 0), (W / 2 + 4 * i, 20 - 2 * i), 'Neon-Glow')
        create_text("2020 Meteora", 20, (165, 0, 0), (W / 2 - 4 * i, 20 + 2 * i), 'Neon-Glow')
    temp = offset // 4 % 5
    create_text("2020 Meteora", 20, (255, 0, 255), (W / 2 + 8 - 4 * temp, 20 - 4 + 2 * temp), 'Neon-Glow')


def gameover():
    for i in range(200):
        pygame.mixer.music.set_volume(1 - i / 200)
    pygame.mixer.music.load(r"music\gameover1.mp3")
    pygame.mixer.music.play(-1)
    for i in range(200):
        pygame.mixer.music.set_volume(i / 400)
        color_t = int(120 * (1 - i / 200))
        cover.fill((color_t, color_t, color_t))
        window.blit(cover, (0, 0))
        pygame.display.update()
    temp = 0
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    for i in range(180, 0, -1):
                        cover.fill((i, i, i))
                        window.blit(cover, (0, 0))
                        pygame.display.update()
                        pygame.mixer.music.set_volume(i / 180 * 0.5)
                    return

        draw_sun((W // 2, H // 2), 120, temp // 3)
        create_text("You Died!!!", 27, (255, 255, 255), (W / 2 + 2, H / 2 - 85 + 2), 'gamer2')
        create_text("You Died!!!", 27, (255, 0, 255), (W / 2, H / 2 - 85), 'gamer2')
        create_text("Your final Score: {}".format(game.score), 27,
                    (255, 255, 255), (W / 2 + 2, H / 2 - 35 + 2), 'gamer2')
        create_text("Your final Score: {}".format(game.score), 27, (255, 0, 255), (W / 2, H / 2 - 35), 'gamer2')
        create_text("press SPACE to restart", 15, (255, 255, 255), (W / 2 + 2, 550 + 2), 'gamer2')
        create_text("press SPACE to restart", 15, (255, 0, 255), (W / 2, 550), 'gamer2')

        pygame.display.update()
        temp += 1


def create_layer():
    global size
    temp = pygame.Surface(size)
    temp.set_colorkey((0, 0, 0))
    return temp


pygame.init()
game = Game()
os.environ['SDL_VIDEO_CENTERED'] = '1'
window = pygame.display.set_mode(size)
pygame.display.set_caption('Retrowave Snake')
clock = pygame.time.Clock()

background = create_layer()
foreground = create_layer()
cover = create_layer()

layers = [background, foreground, cover]  # 序号越往后，越靠上层

start_menu()
while True:
    game_loop()
    gameover()
